"use strict";

const fs = require("fs");
const path = require("path");
const Command = require("@zhuj-cli-dev/command");
const log = require("@zhuj-cli-dev/log");
const inquirer = require("inquirer");
const fse = require("fs-extra");
const semver = require("semver");
const getProjectTemplate = require("./getProjectTemplate");
const Package = require("@zhuj-cli-dev/package");
const userHome = require("user-home");
const { sleep, spinnerStart } = require("@zhuj-cli-dev/utils");
const { execAsync } = require("@zhuj-cli-dev/utils");
const glob = require("glob");
const ejs = require("ejs");

const TYPE_PROJECT = "project";
const TYPE_COMPONENT = "component";

const TEMPLATE_TYPE_NORMAL = "normal";
const TEMPLATE_TYPE_CUSTOM = "custom";
const WHITE_COMMAND = ["npm", "cnpm"];
class InitCommand extends Command {
  init() {
    this.projectName = this._argv[0] || "";
    this.force = !!this._cmd.force;
    log.verbose("InitCommand projectName", this.projectName);
    log.verbose("InitCommand force", this.force);
  }
  async exec() {
    try {
      // 1、准备阶段
      const projectInfo = await this.prepare();
      log.verbose("projectInfo", projectInfo);
      if (projectInfo) {
        // 2、下载模板节点
        this.projectInfo = projectInfo;
        await this.downloadTemplate();
        // 3、模板安装
        await this.installTemplate();
      }
    } catch (e) {
      log.error(e.message);
      if (process.env.Log_level === "verbose") {
        log.error(e.stack);
      }
    }
  }
  async prepare() {
    // 判断模板是否存在
    const template = await getProjectTemplate();

    if (!template || template.length == 0) {
      throw new Error("项目模板不存在，请先去下载模板");
    }
    this.template = template;
    // 1、判断当前目录是否为空
    const localPath = process.cwd();
    const isCurDirEmpty = this.isDirEmpty(localPath);
    if (!isCurDirEmpty) {
      let ifContinue = false;
      // 不是强制更新
      if (!this.force) {
        // 询问是否继续创建
        ifContinue = (
          await inquirer.prompt({
            type: "confirm",
            name: "ifContinue",
            message: "当前文件夹不为空，是否继续创建项目？",
            default: false,
          })
        ).ifContinue;

        if (!ifContinue) return;
      }
      // 2、是否启动强制更新
      if (ifContinue || this.force) {
        // 二次确认
        const { confirmDelete } = await inquirer.prompt({
          type: "confirm",
          name: "confirmDelete",
          message: "是否确认清空当前文件夹？",
          default: false,
        });
        if (confirmDelete) {
          fse.emptyDirSync(localPath);
        }
      }
    }
    return await this.getProjectInfo();
  }

  async downloadTemplate() {
    // 1、通过项目模板API获取项目模板信息
    // 2、通过egg.js搭建一套后端系统
    // 3、通过npm存储项目模块
    // 4、将项目模板信息存储到 mongoDB 数据库中
    // 5、通过egg.js获取 mongoDB 中的数据并且通过API返回
    const templateInfo = this.template.find(
      (item) => item.npmName === this.projectInfo.projectTemplate
    );
    const targetPath = path.resolve(userHome, ".zhuj-cli-dev", "template");
    const storeDir = path.resolve(
      userHome,
      ".zhuj-cli-dev",
      "template",
      "node_modules"
    );
    this.templateInfo = templateInfo;
    const templatePkg = new Package({
      targetPath: targetPath,
      storeDir: storeDir,
      packageName: templateInfo.npmName,
      packageVersion: templateInfo.version,
    });

    if (!(await templatePkg.exists())) {
      const spinner = spinnerStart("正在下载模板...");
      await sleep();
      try {
        await templatePkg.install();
      } catch (error) {
      } finally {
        spinner.stop(true);
        if (templatePkg.exists()) {
          log.success("下载模板成功");
          this.templateNpm = templatePkg;
        }
      }
    } else {
      const spinner = spinnerStart("正在更新模板...");
      await sleep();
      try {
        await templatePkg.update();
      } catch (error) {
      } finally {
        spinner.stop(true);
        if (templatePkg.exists()) {
          log.success("更新模板成功");
          this.templateNpm = templatePkg;
        }
      }
    }
  }

  // 模板安装
  async installTemplate() {
    console.log("安装模板");
    if (this.template) {
      const { type } = this.templateInfo;
      if (!type) {
        throw new Error("模板类型不能为空");
      }

      if (type == TEMPLATE_TYPE_NORMAL) {
        // 标准安装
        this.installNormalTemplate();
      } else if (type == TEMPLATE_TYPE_CUSTOM) {
        // 自定义安装
        this.installCustomTemplate();
      } else {
        throw new Error("无法识别的模板类型");
      }
    } else {
      throw new Error("项目模板不存在");
    }
  }

  // 检测命令是否存在
  checkCommand(cmd) {
    if (WHITE_COMMAND.includes(cmd)) {
      return cmd;
    }
    return nul;
  }

  async execCommand(command, errMsg) {
    let ret;
    if (command) {
      const cmdArr = command.split(" ");
      const cmd = this.checkCommand(cmdArr[0]);
      const args = cmdArr.slice(1);

      if (!cmd) {
        throw new Error("命令不存在" + command);
      }
      ret = await execAsync(cmd, args, {
        stdio: "inherit",
        cwd: process.cwd(),
      });
    }
    if (ret !== 0) {
      throw new Error(errMsg);
    }
    return ret;
  }

  async installNormalTemplate() {
    console.log("安装标准模板", this.templateInfo);
    const spinner = spinnerStart("正在安装模板...");
    await sleep();
    try {
      // 拷贝模板文件到当前目录下
      const templateTarget = path.resolve(
        this.templateNpm.chacheFilePath,
        "template"
      );
      const targetPath = process.cwd();
      // 确保目标目录存在
      fse.ensureDirSync(templateTarget);
      // 确保目标目录存在
      fse.ensureDirSync(targetPath);
      // 拷贝模板文件到当前目录下
      fse.copySync(templateTarget, targetPath);
    } catch (error) {
      throw error;
    } finally {
      spinner.stop(true);
      log.success("模板安装成功");
    }

    const templateIgnore = this.templateInfo.ignore || [];
    const ignore = ["**/node_modules/**", ...templateIgnore];
    await this.esjRender({ ignore });

    // 安装依赖
    const { installCommand, startCommand } = this.templateInfo;
    await this.execCommand(installCommand, "依赖安装过程中失败！");
    await this.execCommand(startCommand, "运行过程中失败！");
  }

  async installCustomTemplate() {
    // 判断路径目录是否存在
    if (await this.templateNpm.exists()) {
      const rootFile = this.templateNpm.getRootFilePath();
      if (fs.existsSync(rootFile)) {
        const templatePath = path.resolve(
          this.templateNpm.chacheFilePath,
          "template"
        );
        const options = {
          templateInfo: { ...this.templateInfo },
          projectInfo: this.projectInfo,
          sourcePath: templatePath,
          targetPath: process.cwd(),
        };
        const code = `require('${rootFile}')(${JSON.stringify(options)})`;
        console.log("🚀 ~ InitCommand ~ installCustomTemplate ~ rootFile:", rootFile)
        await execAsync("node", ["-e", code], {
          stdio: "inherit",
          cwd: process.cwd(),
        });
        log.success("自定义模板安装成功");
      } else {
        throw new Error("自定义模板不存在");
      }
    }
  }
  // 获取项目信息
  async getProjectInfo() {
    // 项目名称是否合适
    function isValidName(v) {
      // 1、首字母必须为英文字母
      // 2、尾只能包含英文字母、数字不能包含字符
      // 3、字符只允许下划线、横线
      return /^[a-zA-Z]+([-][a-zA-Z][a-zA-Z0-9]*|[_][a-zA-Z][a-zA-Z0-9]*|[a-zA-Z0-9])*$/.test(
        v
      );
    }

    let projectInfo = {};
    let isProjectNameValid = false;
    if (isValidName(this.projectName)) {
      isProjectNameValid = true;
      projectInfo.projectName = this.projectName;
    }

    // 3、选择创建项目或组件
    const { type } = await inquirer.prompt({
      type: "list",
      name: "type",
      message: "请选择初始化类型",
      default: TYPE_PROJECT,
      choices: [
        {
          name: "项目",
          value: TYPE_PROJECT,
        },
        {
          name: "组件",
          value: TYPE_COMPONENT,
        },
      ],
    });

    log.verbose("type", type);

    this.template = this.template.filter((template) =>
      template.tag.includes(type)
    );

    const title = type === TYPE_PROJECT ? "项目" : "组件";

    const projectNamePrompt = {
      type: "input",
      name: "projectName",
      message: `请输入${title}名称`,
      default: "",
      validate: function (v) {
        // \w=[a-zA-Z0-9_]
        const done = this.async();
        setTimeout(function () {
          if (!isValidName(v)) {
            done(`请输入合法的${title}名称`);
            return;
          }
          done(null, true);
        }, 0);
      },
      filter: function (v) {
        return v.trim();
      },
    };
    let projectPrompt = [
      {
        type: "input",
        name: "projectVersion",
        message: `请输入${title}版本号`,
        default: "1.0.0",
        validate: function (v) {
          const done = this.async();
          setTimeout(function () {
            if (!!!semver.valid(v)) {
              done(`请输入合法的版本号`);
              return;
            }
            done(null, true);
          }, 0);
        },
        filter: function (v) {
          if (!!semver.valid(v)) {
            return semver.clean(v);
          } else {
            return v;
          }
        },
      },
      {
        type: "list",
        name: "projectTemplate",
        message: `请选择${title}模板`,
        default: "",
        choices: this.createTemplateChoice(),
      },
    ];
    // 不合法的项目名称
    if (!isProjectNameValid) {
      projectPrompt.unshift(projectNamePrompt);
    }

    // 4、获取项目的基本信息
    if (type === TYPE_PROJECT) {
      const project = await inquirer.prompt(projectPrompt);
      projectInfo = {
        ...projectInfo,
        type,
        ...project,
      };
    } else if (type === TYPE_COMPONENT) {
      const descriptionPrompt = {
        type: "input",
        name: "componentDescription",
        message: "请输入组件描述信息",
        default: "",
        validate: function (v) {
          // \w=[a-zA-Z0-9_]
          const done = this.async();
          setTimeout(function () {
            if (!v) {
              done("请输入组件描述信息");
              return;
            }
            done(null, true);
          }, 0);
        },
      };
      projectPrompt.push(descriptionPrompt);
      // 获取组件的基本信息
      projectInfo = await inquirer.prompt(projectPrompt);
    }

    // 生成 classname
    if (projectInfo.projectName) {
      projectInfo.name = projectInfo.projectName;
      projectInfo.className = require("kebab-case")(
        projectInfo.projectName
      ).replace(/^-/, "");
    }
    if (projectInfo.projectVersion) {
      projectInfo.version = projectInfo.projectVersion;
    }
    if (projectInfo.componentDescription) {
      projectInfo.description = projectInfo.componentDescription;
    }
    return projectInfo;
  }

  // esj（% %） 渲染
  async esjRender(options) {
    const dir = process.cwd();
    const projectInfo = this.projectInfo;
    return new Promise((resolve, reject) => {
      glob(
        "**",
        {
          cwd: dir,
          ignore: options.ignore || "",
          nodir: true,
        },
        (err, files) => {
          if (err) {
            reject(err);
          }
          Promise.all(
            files.map((file) => {
              const filePath = path.join(dir, file);
              return new Promise((resolve1, reject1) => {
                ejs.renderFile(filePath, projectInfo, {}, (err, result) => {
                  if (err) {
                    reject1(err);
                  } else {
                    fse.writeFileSync(filePath, result);
                    resolve1(result);
                  }
                });
              });
            })
          )
            .then(() => {
              resolve();
            })
            .catch((err) => {
              reject(err);
            });
        }
      );
    });
  }

  isDirEmpty(localPath) {
    let fileList = fs.readdirSync(localPath);
    // 忽略git文件 和 node_modules文件
    fileList = fileList.filter((file) => {
      return !file.startsWith(".") && ["node_modules"].indexOf(file) < 0;
    });
    return !fileList || fileList.length <= 0;
  }

  createTemplateChoice() {
    return this.template.map((item) => {
      return {
        name: item.name,
        value: item.npmName,
      };
    });
  }
}

function init(argv) {
  return new InitCommand(argv);
}

module.exports = init;
module.exports.InitCommand = InitCommand;
